BT

最新技術を追い求めるデベロッパのための情報コミュニティ

寄稿

Topics

地域を選ぶ

InfoQ ホームページ アーティクル "あなたを後で呼び返していいですか?" サービスコンポーネントアーキテクチャを使用した非同期サービスの構築

"あなたを後で呼び返していいですか?" サービスコンポーネントアーキテクチャを使用した非同期サービスの構築

(IBM のMike Edwards氏による)この記事では、サービス指向アーキテクチャを用いたアプリケーション構築における非同期サービスの必要性について論じています。非同期サービスの構築は複雑になりがちですが、サービスコンポーネントアーキテクチャ(SCA) を用いると単純にすることができます。この記事ではSCAを用いて、非同期サービスとそのクライアントを作るためのステップについてお話しします。

SCA についての一般的なイントロダクションは{1}を見てください。

業務プロセスと、非同期サービスの必要性

「物事が今すぐに、迅速に完了するのは素晴らしいことである」と私たちは誰もが考えています。しかし実際の生活では、物事は順番づけられた複数のステップを必要とし、処理に時間がかかることはしばしばあります。

何冊かの本をオンラインで注文することを考えてください。前もって欲しい本を見つけるだけではなく、注文できるかどうか、予定される発送日、他の配送方法についてチェックすることもあるでしょう。一度注文が行われ、支払いを終えたとしても、物事はすぐに終わるわけではありません:

  • まず本の売り手は、予定される発送日とともに注文確認を送ります。あなたは注文の状態をチェックできる何らかの注文IDを受け取ります。
  • いくらか後になって、本の売り手はあなたに対して注文の発送確認を送ります。それには、運送業者の配送状態をチェックすることのできるトラッキングナンバーが付随しています。
  • 最後に、もしあなたがラッキーなら、本が到着します!

多くの種類の業務プロセスがこうした特徴を持ちます。こうした業務プロセスをサポートするアプリケーションを作ろうとする場合、サービスプログラミングにおける典型的な"call-and-return"スタイルが、うまく機能しないことがはっきりしてきます。

Call-and-return - 同期処理としてもよく知られています - は、サービスのクライアントがサービスを呼び出した後停止し、クライアントコードが続きの処理を行う前に、サービスがタスクを完了するまで待つと言うことを意味しています。もしクライアントのコードがこれ以上何もしない場合、そのコードは何もしないのにメモリに居続けることになり、クライアントが動作しているシステムにとっては単なる負担となります。クライアントコードがシステム上に居続けるだけなら問題ではありませんが - システムが、1分間に数千のクライアントリクエストを取り扱っているときには、即座にトラブルを引き起こします。

その他のケースとしては、サービスへの呼び出しが完了するのを待つ間、クライアントコードは他のことを実行できると言う場合があります。休暇の準備を行うクライアントコードは、飛行機、ホテル、レンタルカーの準備を行うサービスを呼び出す必要があります。こうした場合、一度に一つの呼び出ししか行わず、次の呼び出しに移る前に前の呼び出しが完了するのを待つよりも、並行してリクエストを行う方がずっと速くなります。

こうしたケースは非同期処理と呼ばれます。あなたはこれを、クライアントがサービスに向けてリクエストメッセージを発行した後、何か新しい作業を始める事だと考えることができます。呼び出されたサービスは自身の作業を行い、完了したら、クライアントにレスポンスメッセージを発行します。もしくは私たちが書籍の注文処理で見たように、サービスはクライアントに対して一つ以上のレスポンスメッセージを発行することもできます。一つは注文の確認メッセージで、もう一つのメッセージは注文の発送連絡でした。

非同期サービスを作るための環境

非同期サービスが必要だとすると、そうしたサービス - そしてクライアント- を作る上で手助けになるような環境にはどんなものがあるでしょうか?

同期サービスのクライアントを書くための環境は、ほとんどのプログラミングモデルとフレームワークでよく提供されています。同じ事がサービスにも言えます。同期のcall-and-returnモデルは、ほとんどのプログラミング言語でコードを記述するときの標準的な形式です。結果として、同期サービスや同期サービスのクライアントは通常、標準的なプログラミングモデルに対するシンプルな拡張以上のものではありません。例えばJavaでは、サービスはクラス内のメソッドとして実装され、クラスのメソッドを呼び出すことがサービスのクライアントである、と言うことを通常は意味しています。

非同期のサービスとクライアントを書く環境は、同期のものほど開発されていません。多くのプログラミング言語の中でも、Javaは典型的です。非同期プログラミングに対する上質のサポートが追加されたのは - Java 5における並行処理クラスの出現によって - 相対的に見てつい最近です。

またいくつかのプロトコルに関連したプログラミングモデル[2]を通じて、非同期処理を行うための仕組みは存在します。例えば、JAX-WS [5]はWebサービスをクライアントとサービスの間のコミュニケーション方法として用い、同期のサービスに対する非同期クライアントを記述する方法を提供しています。

この場合、もし同期サービス呼び出しのためのクライアントコードは以下のようになります。

OrderResponse placeOrder( OrderRequest oRequest );

例1: 単純な同期によるplaceOrder操作

同等の、非同期サービス呼び出しは以下のようになります。

Future placeOrderAsync( OrderRequest oRequest,                            
AsyncHandler responseHandler );

例2: placeOrderリクエストの非同期な形式

そして、その呼び出しに対する非同期のレスポンスは、以下のようなハンドラを通じてクライアントに戻ってきます。

class OrderResponseHandler implements AsyncHandler {

public void handleResponse( Response theResponse ){

OrderResponse orderResponse = theResponse.get();
}

}

例3: placeOrderが完了したときに呼び出されるハンドラメソッド

JAX_WSは、非同期サービスを書くためのサポートを提供していません。また、一つのリクエストに対して、正確に一つのレスポンスメッセージが存在する場合しかサポートしていません。

非同期スタイルのクライアント、非同期サービスのどちらもサポートしているJavaフレームワークはJMS API [6]です。クライアントとサービスの間のコミュニケーション方法が、JMSをサポートしたメッセージングシステムである場合、JMS APIが利用できます。JMSはまた、Java EEフレームワークにおいて、Message Driven Beans [7]と言う方法でも利用可能です。JMSのアプローチは非同期サービスをサポートしていますが、JMSはクライアントとサービスのコードをいくつかのメッセージ基盤の使用に限定してしまいます。

最後に、非同期の業務プロセスも考慮に入れて開発された完全な言語があります。これはBusiness Process Execution Language (略してBPEL) [8]です。BPELは同期/非同期にかかわらず、サービスのグループを協調させる際に特に有効です。しかし、巨大な量のデータ操作を引き起こすサービスやクライアントを記述するのに関しては、恐らくあまり向いていません。

SCAと非同期サービス

SCA は、非同期のサービスと、非同期サービスのクライアントを作成するのに、第一級のサポートを提供しています。SCAは、単純な対話パターン(各リクエストメッセージに対して一つのレスポンスメッセージ)による非同期サービスをサポートしているだけではなく、より複雑な対話パターン(各リクエストメッセージに対してゼロ、一つ、もしくは複数のレスポンスメッセージ)もサポートしています - 私たちの"シンプルな"書籍注文プロセスが、書籍注文の各リクエストに対して二つのレスポンスメッセージを生成していたことを思い出してください。

SCA は、広範囲にわたるプログラミング言語で書かれたあらゆる非同期サービスをサポートできます。JavaやBPELがその例です。加えて、SCAはクライアントと(サービス)プロバイダの間で、様々な種類のコミュニケーション手段を差し替え可能です - (JMSによって使用されるような)メッセージ基盤を使えますし、代わりにWebサービス基盤を使うこともできます。SCAは、クライアントとサービスプロバイダのコードを、いかなる特定のコミュニケーション基盤にも限定しません。

SCAは、サービスクライアントとサービスプロバイダの間のコミュニケーションを、次のようなコンセプトでモデル化しています。

  • サービスは一つ以上のオペレーションを含むインターフェースであり、サービスプロバイダによって実装される
  • リファレンスはサービスプロバイダのオペレーションを呼び出すために、クライアントによって使用される。リファレンスは、サービスプロバイダが実装したものと同じインターフェースを実装するプロキシオブジェクト、というのが典型的である。

同期サービスの場合、インターフェース内の各オペレーションはcall-and-returnスタイルを採ります。メッセージはサービスプロバイダに対してオペレーションの引数として送信され、レスポンスはオペレーションの戻り値となります。:

OrderResponse placeOrder( OrderRequest oRequest );

例4: 単純な同期placeOrderオペレーション

非同期サービスについては、SCAはコールバックのコンセプトを持ちます。コールバックが暗に意味しているのは、コミュニケーションが双方向で非同期的である、と言うことです。クライアントは、オペレーションを使用してサービスプロバイダを呼び出します - レスポンスは、分割されたサービス・インターフェースコールバック・インターフェースを用いて、サービスプロバイダがクライアントを呼び返す事によって生じます。サービスプロバイダがサービスインターフェースを実装している限り、クライアントはコールバックインターフェースを実装しなくてはなりません。

例4で見せた同期オペレーションと同等の非同期コールバックは、以下のようなリクエストオペレーションからなります。:

void placeOrder( OrderRequest oRequest );

例5: 非同期のplaceOrderサービスリクエスト

...コールバックインターフェース内の、対になるオペレーションは以下のようになります:

void placeOrderResponse( OrderReponse oResponse );

例6: placeOrderレスポンスのためのコールバックオペレーション

非同期サービスに対してSCAが採っているアプローチの中でも特に重要なのは、クライアントによる最初のサービス呼び出し以降、コールバックインターフェースを通じて、クライアントはサービスプロバイダによっていつでもコールバックされる可能性がある、と言うことです。サービスプロバイダは、コールバックインターフェースに定義されているいかなるオペレーションを使うこともできますし、クライアントからの単一のリクエストに対するレスポンスの中で、プロバイダは一つのオペレーションを呼び出すことも、複数のオペレーションを呼び出すことも、オペレーションを呼び出さないようにすることもできるのです。最も単純なケースでは、クライアントから呼び出されたサービスオペレーションの各々で、プロバイダはコールバックインターフェース内のオペレーションを正確に一つだけ呼び出すことです。

シンプルな例

クライアントによって非同期サービスが使用される、単純な例を見ていきましょう。この例では、クライアントとサービスはどちらもプレーンなJavaクラスで書かれています。これはいくつかの商品を注文するサービスのシンプルなバージョンです。サービスは非同期で、クライアントはコールバックインターフェースを通じてその進捗状態を知らされます。

まず最初に、サービスとコールバックについてのインターフェースが存在します。

public interface OrderService {
public void placeOrder( OrderRequest oRequest );
}

例7: OrderServiceインターフェース

public interface OrderCallback {
public void placeOrderResponse( OrderResponse oResponse );
}

例8: OrderCallbackインターフェース

次に、OrderServiceの実装コードが存在します。

import org.osoa.sca.annotations.*;  

public class OrderServiceImpl implements OrderService {

// A field for the callback reference object
private OrderCallback callbackReference;

// The place order operation itself
public void placeOrder( OrderRequest oRequest ) {
// …do the work to process the order…
  // …which may take some time…

  // when ready to respond…

  OrderResponse theResponse = new OrderResponse();

 callbackReference.placeOrderResponse( theResponse );
}

// A setter method for the callback reference

@Callback
public void setCallbackReference( OrderCallback theCallback ) {
callbackReference = theCallback;
  }
}

例9: OrderServiceの実装

最後に、OrderServiceのクライアントコードです。OrderCallbackインターフェースを実装しています。

import org.osoa.sca.annotations.*;

public class OrderServiceClient implements OrderCallback {

// A field to hold the reference to the order service

 private OrderService orderService;

 public void doSomeOrdering() {

 OrderRequest oRequest = new OrderRequest();

//… fill in the details of the order …

 orderService.placeOrder( oRequest );

// …the client code can continue to do processing
 }

 public void placeOrderResponse( OrderResponse oResponse ) {

 // …handle the response as needed
}

// A setter method for the order service reference

@Reference
public void setOrderService( OrderService theService ) {
orderService = theService;
}
}

例10: OrderServiceのクライアント

サービスの実装

最初に、例9に示されている非同期サービスのコードを見ていきましょう。

@Callback アノテーションが付与された、setCallbackReferenceと呼ばれるsetterメソッドがあります。@Callbackアノテーションは、コールバックインターフェースが表しているリファレンスオブジェクトをクライアントにインジェクトするよう、SCAランタイムに対して指示します - これは、サービスがplaceOrderオペレーションとしてリクエストを受け取ったときに行われます。これにより、サービス実装はクライアントにコールバックを行うことができるようになります。

サービス実装は、自身のアイデンティティやクライアントの位置に関心を払う必要はありません - これはSCAランタイムによって解決され、こうした情報のすべては、渡されるcallbackReferenceオブジェクトに含まれます。

サービスのオペレーションであるplaceOrderですが、単にOrderRequestメッセージを受け取り、注文を処理するための作業を行います。この処理にどれだけの時間を必要とするかはわかりません - 数秒以内かもしれませんし、数日以内かもしれませんが、placeOrderオペレーションがクライアントにレスポンスを返す準備ができればいつでも、 callbackReferenceを使用します。callbackReferenceは、クライアントへのコールバックを行うための placeOrderResponseオペレーションを提供します。

もし必要なら、OrderServiceの実装は callbackReferenceのコピーを保存して書き出しておき、必要なときに読み出すこともできます。これはおそらく、次のような場合において正しいアプローチです - たとえばもし、注文された品を梱包すると言ったような人的タスクがプロセスの一部だったり、OrderServiceが、タスクを実行する前にいくつかの人手による入力を待つ必要がある場合などです。

サービスのクライアント

OrderServiceに対するクライアントのコードは、例10でお見せしました。

これは二つの主要な部分からなります - placeOrderサービスを呼び出すdoSomeOrderingメソッド、そしてplaceOrderリクエストからのコールバックを処理する placeOrderResponseメソッドです。また、orderServiceの参照に対するsetterメソッドも存在します。このメソッドは、 @Referenceアノテーションでマークされていることに注意してください。@Referenceアノテーションは、クライアントが動作を開始する前に、OrderServiceへの参照を寄越すよう、SCAランタイムに対して指示を行うものです。参照されるオブジェクトは、OrderService への業務インターフェースを提供します。サービスの場所に関するすべての情報や、サービスとの対話に用いられるコミュニケーション方法などは、そのオブジェクト内に保持されています。

クライアントは、doSomeOrderingメソッドの中で、OrderRequestの準備を行います。その準備が完了した時点で、リファレンスオブジェクトorderServiceのオペレーションを呼び出すことで、OrderServiceのplaceOrderオペレーションを呼び出します。これがOrderServiceに対するリクエストの送信となります。クライアントコードはOrderServiceからのレスポンスを一切待つことなく、 placeOrderサービスから即座にリターンします。クライアントコードはほかの処理に移行したり、初期化作業が完了したなら、単純にリターンすることもできます。

元になったplaceOrderリクエストに対して、OrderServiceがレスポンスを返すと、OrderResponseメッセージがクライアントのplaceOrderResponseメソッドに届きます。

placeOrderResponseメソッドは、レスポンスを処理するのに必要なあらゆる作業を行うことができます - たとえば、それをデータベースに記録することができます。

placeOrderResponse メソッドは、元になったplaceOrderリクエストが生成されてから、長い時間がたった後に呼び出されるかもしれません。レスポンスとその元になったリクエストを、クライアントが紐づけるのに必要なすべての情報がレスポンスには含まれることでしょう。そうした場合OrderResonseメッセージは、元になったOrderRequestメッセージと関連づけられるための情報(たとえば注文番号)を含むでしょう。

しかし、 OrderResponseメッセージが元になったリクエストと簡単には紐付かないと言う場合もあり得ます。こうした場合クライアントコードは、元になるリクエストに対してコールバックIDを付与することができます。コールバックIDはレスポンスメッセージによってリターンされます。コールバックIDは、元になったリクエストを一意に識別するためのラベルとして使用される、シリアライズ可能で単純なオブジェクト(たとえば文字列)です。そしてこのIDを使ってクライアントは元になったリクエストを識別することができ、その時点での状態データを再度参照するのに使用することができます。

クライアントとサービスの間の接続

あなたがおそらく抱いている質問の一つはおそらく - "クライアントとサービスの間の接続は、実際にはどのように定義されるのだろう?"ではないでしょうか。SCAランタイムによってインジェクトされ、到達可能になるサービスやコールバックのリファレンスにより、クライアントのコードとサービスのコードはいくらか魔術めいているように見えます。

SCAは、ソリューションを構築するためにコンポーネントのグループをいかに構成し、それらをつなぎ合わせるかを定義するためのアセンブリモデル [3]を定義しています。サービスやクライアントの位置や、それらがお互いに接続するための方法を定義するのに必要とされるすべての情報は、それらをソリューションとして構築したコンポジットによってもたらされます。SCAランタイムはコンポジットから情報を読み取り、リファレンスとコールバックのプロキシを提供する際に使用します。それらのプロキシは、クライアントとサービスのコードによって実行時に使用されます。

SCA Assembly diagram for simple Client and Service

図1: 単純なクライアントとサービスに関するSCAアセンブリ図

SCA は、図を用いてコンポジットを表現することができます - 図は、1000の言葉よりも価値があります!図1に表された図は、OrderClientとOrderServiceからなるアセンブリを記述するためのものです。OrderClientにとっては、"OrderService"と言う名前のリファレンスが存在します。OrderServiceにおいては、"OrderService"と言う名のサービスが存在します。リファレンスはサービスに向けてワイアで接続されており、クライアントのリファレンスがサービスと接続されている、と言うことを表しています。この接続がコールバックを発生させるという事実は、サービスを記述するために使用されているインターフェースで定義されています - そのインターフェースはOrderServiceインターフェースとOrderCallbackインターフェースの両方からなる"デュアルインターフェース"です。

"一皮むくと"、SCAはコンポジットをXMLメタデータで表現しており、リファレンスとサービスをリンクさせるのに使用されるコミュニケーション方法と言ったほかの情報も含むことができます。前述の図に匹敵するXMLは、次のようなものになるでしょう。

version="1.0" encoding="UTF-8"?>

<composite name="orderComposite"

      xmlns:sca="http://www.osoa.org/xmlns/sca/1.0">

 

  <component name="OrderClient">

      <implementation.java class="OrderServiceClient"/>

      <reference name="OrderService"

                 target="OrderService/OrderService">

            <binding.ws/>

      reference>

   component>

 

  <component name="OrderService">

      <implementation.java class="OrderServiceImpl"/>

      <service name="OrderService">

            <interface.java interface="OrderService"

                  callbackinterface="OrderCallback"/>

            <binding.ws/>

      >

  > 

>

例11: OrderClientとOrderServiceの間がリンクされているコンポジット

この場合、OrderClientはreference要素における@target属性の値を通じて、OrderServiceコンポーネントの OrderServiceサービスに紐付いた、OrderServiceのリファレンスを持ちます。OrderClientのreference要素内と、OrderServiceのservice要素内のどちらにも存在する(binding.ws)要素は、これらの間のコミュニケーションにWebサービスを用いることを指示しています。

まとめ

最初のリクエストが生成されてから、長い時間を経た後レスポンスが返ってくるような非同期サービスは、日常的によくあることです - 全てが即座に起こるわけではないのです!

Service Component Architectureは、非同期サービスの実装と非同期サービスのクライアントを提供するのをより単純にします。SCAは、非同期サービスに対してコールバックレスポンスを伴うリクエストというモデルを提供し、コールバックIDによって、元になったリクエストとコールバックレスポンスを紐づけることもできます。

SCAは、クライアント・サービス間での様々な種類のコミュニケーション方法に対してこれを行うことが可能です。複雑なミドルウェアAPIに依存するコードは、もはや必要ありません。

SCAを実際に試し、それがサービス指向アプリケーションのプログラミングをいかに単純化するかをあなた自身の目で確かめたいのであれば、様々な企業から商用製品が提供されています。それらは、以下のページで見ることができます。

http://www.osoa.org/display/Main/Implementation+Examples+and+Tools (英語)

また、SCAのオープンソース実装も存在します。

Apache Tuscany: http://cwiki.apache.org/TUSCANY/ (英語)
Fabric3: http://fabric3.codehaus.org/ (英語)

参考

[1] "サービスコンポーネントアーキテクチャを始める" http://www.infoq.com/jp/articles/setting-out-for-sca
[2] "Axis2による非同期Webサービスの開発" http://www.ibm.com/developerworks/webservices/library/ws-axis2 (英語)
[3] SCAアセンブリモデル仕様http://www.osoa.org/download/attachments/35/SCA_AssemblyModel_V100.pdf (PDF・英語)
[4] SCA Java共通アノテーション・API仕様http://www.osoa.org/download/attachments/35/SCA_JavaAnnotationsAndAPIs_V100.pdf (PDF・英語)
[5] JAX-WS仕様 http://jcp.org/aboutJava/communityprocess/mrel/jsr224/index2.html  (英語)
[6] JMS仕様 http://java.sun.com/products/jms/docs.html (英語)
[7] EJB 3.0におけるMessage Driven Beansの仕様http://jcp.org/aboutJava/communityprocess/final/jsr244/index.html (英語)
[8] Business Process Execution Language (BPEL) http://docs.oasis-open.org/wsbpel/2.0/wsbpel-v2.0.pdf (PDF・英語)

著者について

Mike Edwardsは、イギリスのWinchesterに近い、IBMのHursley研究所において、技術の新規開発戦略を担当しています。彼は現在SOA と、その中でも特にService Component Architectureの使用に従事しています。彼はOASISのSCAアセンブリ技術委員会の共同議長であり、Apache Tuscanyプロジェクトのコミッタでもあります。

原文はこちらです:http://www.infoq.com/articles/async-sca
(このArticleは2008年1月23日に原文が掲載されました)

この記事に星をつける

おすすめ度
スタイル

BT